package com.qs.commontools.utils.date;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * 日期操作类。
 * <p>需要设置操作对象Date，默认是当前时间，基于这个操作对象,进行一系列的操作</p>
 * @author qiusuo
 * @since 2021-11-01
 */
public class DateTool {

    /**
     * 操作日期
     */
    private Date date;
    /**
     * 日历对象
     */
    private Calendar calendar;
    /**
     * 格式化规则
     */
    private String format;
    /**
     * 格式化对象
     */
    private SimpleDateFormat simpleDateFormat;
    /**
     * 操作加减:年
     */
    private Integer year;
    /**
     * 操作加减:月
     */
    private Integer month;
    /**
     * 操作加减:日
     */
    private Integer day;
    /**
     * 操作加减:时
     */
    private Integer hour;
    /**
     * 操作加减:分
     */
    private Integer minute;
    /**
     * 操作加减:秒
     */
    private Integer second;


    //-----------------------------------------------------------------------
    // todo 获得实例对象
    //-----------------------------------------------------------------------

    public static DateTool getInstance() {
        return getInstance(new Date());
    }

    public static DateTool getInstance(Date date) {
        DateTool tool = new DateTool();
        tool.setDate(date);
        return tool;
    }

    //-----------------------------------------------------------------------
    // todo set&get方法
    //-----------------------------------------------------------------------

    /**
     * 获取经过处理后的日期。
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @return 最终的结果Date对象
     */
    public Date getDate() {
        // 处理日历对象
        handleCalendar();
        return calendar.getTime();
    }

    /**
     * 设置需要处理的日期，默认是当前时间。
     *
     * @param date 要日期对象
     * @return 当前的日期操作类
     */
    public DateTool setDate(Date date) {
        // 设置日历对象，日历对象绑定时间
        if (this.calendar == null) {
            calendar = Calendar.getInstance();
        }
        calendar.setTime(date);
        this.date = date;
        return this;
    }

    /**
     * 处理日历对象
     */
    private void handleCalendar(){
        // 日历对象重置
        this.calendar.setTime(this.date);
        if (null != this.year) {
            this.calendar.add(Calendar.YEAR, this.year);
        }
        if (null != this.month) {
            this.calendar.add(Calendar.MONTH, this.month);
        }
        if (null != this.day) {
            this.calendar.add(Calendar.DAY_OF_MONTH, this.day);
        }
        if (null != this.hour) {
            this.calendar.add(Calendar.HOUR_OF_DAY, this.hour);
        }
        if (null != this.minute) {
            this.calendar.add(Calendar.MINUTE, this.minute);
        }
        if (null != this.second) {
            this.calendar.add(Calendar.SECOND, this.second);
        }
    }

    /**
     * 设置格式化的标准，如"yyyy-MM-dd HH:mm:ss"。
     *
     * @param format 标准格式化字符串
     * @return 当前的日期操作类
     */
    public DateTool setFormat(String format) {
        this.simpleDateFormat = new SimpleDateFormat(format);
        this.format = format;
        return this;
    }

    /**
     * 对设置的日期或默认日期进行年份的加减。
     *
     * @param year 年份的加减数，正数增加年份，负数减少年份
     * @return 当前的日期操作类
     */
    public DateTool setYear(Integer year) {
        this.year = year;
        return this;
    }

    /**
     * 对设置的日期或默认日期进行月份的加减。
     *
     * @param month 月份的加减数，正数增加月份，负数减少月份
     * @return 当前的日期操作类
     */
    public DateTool setMonth(Integer month) {
        this.month = month;
        return this;
    }

    /**
     * 对设置的日期或默认日期进行天数的加减。
     *
     * @param day 天数的加减数，正数增加天数，负数减少天数
     * @return 当前的日期操作类
     */
    public DateTool setDay(Integer day) {
        this.day = day;
        return this;
    }

    /**
     * 对设置的日期或默认日期进行小时的加减。
     *
     * @param hour 小时的加减数，正数增加小时，负数减少小时
     * @return 当前的日期操作类
     */
    public DateTool setHour(Integer hour) {
        this.hour = hour;
        return this;
    }

    /**
     * 对设置的日期或默认日期进行分钟的加减。
     *
     * @param minute 分钟的加减数，正数增加分钟，负数减少分钟
     * @return 当前的日期操作类
     */
    public DateTool setMinute(Integer minute) {
        this.minute = minute;
        return this;
    }

    /**
     * 对设置的日期或默认日期进行秒数的加减。
     *
     * @param second 秒数的加减数，正数增加秒数，负数减少秒数
     * @return 当前的日期操作类
     */
    public DateTool setSecond(Integer second) {
        this.second = second;
        return this;
    }

    /**
     * 获得处理后的年份。
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @return 年份
     */
    public Integer getYear() {
        handleCalendar();
        return calendar.get(Calendar.YEAR);
    }

    /**
     * 获得处理后的月份。
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @return 月份
     */
    public Integer getMonth() {
        handleCalendar();
        return calendar.get(Calendar.MONTH)+1;
    }

    /**
     * 获得处理后的日期。
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @return 日期
     */
    public Integer getDay() {
        handleCalendar();
        return calendar.get(Calendar.DAY_OF_MONTH);
    }

    /**
     * 获得处理后的小时。
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @return 小时
     */
    public Integer getHour() {
        handleCalendar();
        return calendar.get(Calendar.HOUR_OF_DAY);
    }

    /**
     * 获得处理后的分钟。
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @return 分钟
     */
    public Integer getMinute() {
        handleCalendar();
        return calendar.get(Calendar.MINUTE);
    }

    /**
     * 获得处理后的秒。
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @return 秒
     */
    public Integer getSecond() {
        handleCalendar();
        return calendar.get(Calendar.SECOND);
    }

    /**
     * 获取处理好的格式化后的时间字符串。
     * <br>假如未调用{@linkplain DateTool#setFormat(String) setFormat} 方法，默认格式："yyyy-MM-dd HH:mm:ss"
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @return 格式化后的时间字符串
     */
    public String getFormat() {
        if (this.simpleDateFormat == null) {
            this.simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
        return this.simpleDateFormat.format(getDate());
    }

    /**
     * 获取指定格式化后的时间字符串。
     * <br>获取时间字符串的同时设置了格式化格式,内部调用{@linkplain DateTool#setFormat(String) setFormat} 方法
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @param format 格式化格式，如"yyyy-MM-dd HH:mm:ss"
     * @return 格式化后的时间字符串
     */
    public String getFormat(String format) {
        setFormat(format);
        return this.simpleDateFormat.format(getDate());
    }



    //-----------------------------------------------------------------------
    // todo 私有化
    //-----------------------------------------------------------------------

    private DateTool() {
    }

    //-----------------------------------------------------------------------
    // todo 方法
    //-----------------------------------------------------------------------

    /**
     * 处理后的日期是星期几。
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @return 返回中文如"星期一"
     */
    public String getWeek() {
        handleCalendar();
        int weekDay = calendar.get(Calendar.DAY_OF_WEEK);
        switch (weekDay) {
            case Calendar.MONDAY:
                return "星期一";
            case Calendar.TUESDAY:
                return "星期二";
            case Calendar.WEDNESDAY:
                return "星期三";
            case Calendar.THURSDAY:
                return "星期四";
            case Calendar.FRIDAY:
                return "星期五";
            case Calendar.SATURDAY:
                return "星期六";
            default:
                return "星期日";
        }
    }

    /**
     * 处理后的日期是一周中的第几天，从周日开始为第一天。
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @return 第几天
     */
    public Integer getDayOfWeek() {
        handleCalendar();
        return calendar.get(Calendar.DAY_OF_WEEK);
    }

    /**
     * 处理后的日期是一年中的第几天,从1月1日开始算第一天。
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @return 第几天
     */
    public Integer getDayOfYear() {
        handleCalendar();
        return calendar.get(Calendar.DAY_OF_YEAR);
    }

    /**
     * 处理后的日期是一年中的第几周，该方法是以中国计算方式，周一为一周的开始。
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @return 第几周
     */
    public Integer getWeekOfYear() {
        handleCalendar();
        return getWeekOfYear(true);
    }

    /**
     * 处理后的日期是一年中的第几周。
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @param isChina 是否很据中国日期来计算，<code>true</code>以周一为一周的开始计算,<code>false</code>以周末为一周的开始计算
     * @return 第几周
     */
    public Integer getWeekOfYear(boolean isChina) {
        handleCalendar();
        // 假如按照中国的星期算
        if (isChina) {
            // 将周一设置为一周的开始
            calendar.setFirstDayOfWeek(Calendar.MONDAY);
        }
        return calendar.get(Calendar.WEEK_OF_YEAR);
    }

    /**
     * 根据处理后的日期获得当月的天数。
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @return 天数
     */
    public Integer getDaysOfMonth() {
        handleCalendar();
        return calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
    }

    /**
     * 检验处理好的日期所在年份是否是闰年。
     * <br>1、通过&nbsp;{@linkplain DateTool#setDate(Date) setDate}&nbsp;设置需要处理的Date对象
     * <br>2、通过以下方法对设置的日期进行加减的处理：
     * <ul>
     *     <li>{@linkplain DateTool#setYear(Integer) setYear}&nbsp;设置年份的加减</li>
     *     <li>{@linkplain DateTool#setMonth(Integer) setMonth}&nbsp;设置月份的加减</li>
     *     <li>{@linkplain DateTool#setDay(Integer) setDay}&nbsp;设置日期的加减</li>
     *     <li>{@linkplain DateTool#setHour(Integer) setHour}&nbsp;设置小时的加减</li>
     *     <li>{@linkplain DateTool#setMinute(Integer) setMinute}&nbsp;设置分钟的加减</li>
     *     <li>{@linkplain DateTool#setSecond(Integer) setSecond}&nbsp;设置秒数的加减</li>
     * </ul>
     *
     * @return <code>true</code>是闰年，<code>false</code>不是闰年
     */
    public boolean checkLeapYear() {
        handleCalendar();
        return calendar.getActualMaximum(Calendar.DAY_OF_YEAR) == 366;
    }
}
